function InternalSocket (url) {
    this.url = url;
    this._socket = null;
    this.handlers = {};
    this._heartBeatTimer = null;
    this._reconnetTimer = null;
    this._timeout = null;
    this._first = 1;
    this._retryTimes = 0;
    this._retry = 1;
};

InternalSocket.prototype.setRetry = function(isRetry){
    this._retry = isRetry;
};

InternalSocket.prototype.getRetry = function(){
    return this._retry;
};

InternalSocket.prototype.connect = function () {
    var self = this;
    if (self._socket !== null) return;
    cc.log(self.url);
    self._socket = new WebSocket(self.url);
    self._socket.binaryType = "arraybuffer"; //blob
    self._socket.onopen = self.onopen.bind(this);
    self._socket.onmessage = self.onmessage.bind(this);
    self._socket.onerror = self.onerror.bind(this);
    self._socket.onclose = self.onclose.bind(this);
};

InternalSocket.prototype.onopen = function (event) {
    cc.log("client connection establish.");
    var self = this;
    self.clearHBTimer();
    self.clearReconnetTimer();
    self.heartBeat();
    if (this._first) {
        this._first = 0;
        self._handle(cc.SYS_MSG.CONNECT, {});
    }
    else {
        self._handle(cc.SYS_MSG.RECONNECT, {});
    }
    this._retryTimes = 0;
};

InternalSocket.prototype.onmessage = function (event) {
    //cc.log("recv data " + event.type);
    var self = this;
    var orig_buf = event.data;
    if (cc.sys.isNative) {
        self.onmessageNative(orig_buf);
    } 
    else {
        self.onmessageOther(orig_buf);
    }
};

InternalSocket.prototype.onerror = function (event) {
    var self = this;
    cc.log("websocket error . State : " + self.readyState());
    cc.log(JSON.stringify(event));
    //if (event.type == "error") {};
    self.clearHBTimer();
    if (!!self._socket)
        self._handle(cc.SYS_MSG.ERROR, { readyState: self._socket.readyState });
};

InternalSocket.prototype.onclose = function (event) {
    var self = this;
    cc.log("websocket close . State : " + self.readyState());
    cc.log(JSON.stringify(event));
    self.clearHBTimer();
    self._handle(cc.SYS_MSG.CLOSE, {});
    self._socket = null;

    if (self._retryTimes < cc.MAX_RECONNECT_TIMES) {
        //self._retryTimes++;
        if (self._reconnetTimer === null) {
            self._reconnetTimer = setInterval(function () {
                if (self._retry == 0) return;
                self.connect();
            }, cc.RECONNECT_TIME);
        }
    }
};

InternalSocket.prototype._handle = function (msgid, msg) {
    var self = this;
    var _handlers = self.handlers[msgid];
    if (_handlers) {
        for (var i = 0; i < _handlers.length; i++) {
            var callBack = _handlers[i].cb;
            if (!!callBack && typeof callBack == 'function') {
                callBack(msg, self);
            } 
        }
    }
};

//app端数据接收
InternalSocket.prototype.onmessageNative = function (buf) {
    var self = this;
    var orig_len = buf.byteLength;//获取字节长度
    var dv = new DataView(buf);//使用DataView视图从ArrayBuffer对象中读取数据
    var msgid = dv.getInt16(0, false); //将前两个字节读取为消息号，false表示大端字节序
    if ((orig_len - 2) >= 0) {//剩下的字节数要大于等于0
        var dist_buf = new ArrayBuffer(orig_len - 2);//新建一段内存
        var msg_buf = new Uint8Array(dist_buf);
        var offset = 2;
        //下面把剩下的消息体取到msg_buf中
        for (var i = 0; i < orig_len - 2; i++) {        
            msg_buf[i] = dv.getUint8(offset, false);
            offset++;
        }
        //找到具体对应的protobuf解析
        var msg_root = cc.protoRoot[msgid];
        if (msg_root) {
            cc.log("recv msgid ---> " + msgid + " : " + msg_root.name);
            var msg = msg_root.decode(msg_buf);
            //得到一个json消息数据。
            try {
                self._handle(msgid, msg);
            }
            catch (err) {
                throw err;
            }
        }
        else {
            cc.log("decode msg fail.");
        }
    }
    else {
        cc.log("msg length error");
    }
};

//h5数据接收
InternalSocket.prototype.onmessageOther = function (buf) {
    var self = this;
    //var reader = new FileReader();
    var reader = {};
    //reader.readAsArrayBuffer(buf);
    //reader.onload = function (e) {
        if (false) {
            console.log("err");
            console.log(e);
        }
        else {
            reader.result = buf;
            //console.info(reader.result); //ArrayBuffer {}
            //经常会遇到的异常 Uncaught RangeError: byte length of Int16Array should be a multiple of 2
            //var buf = new int16array(reader.result);
            //console.info(buf);
            var orig_len = reader.result.byteLength;
            var dv = new DataView(reader.result);
            var msgid = dv.getInt16(0, false);
            if ((orig_len - 2) >= 0) {
                var dist_buf = new ArrayBuffer(orig_len - 2);
                var msg_buf = new Uint8Array(dist_buf);
                var offset = 2;
                for (var i = 0; i < orig_len - 2; i++) {        
                    msg_buf[i] = dv.getUint8(offset, false);
                    offset++;
                }
                var msg_root = cc.protoRoot[msgid];
                if (msg_root) {
                    console.log("recv msgid ---> " + msgid + " : " + msg_root.name);
                    var msg = msg_root.decode(msg_buf);
                    try {
                        self._handle(msgid, msg);
                    }
                    catch (err) {
                        throw err;
                    }
                }
                else {
                    console.log("decode msg fail.");
                }
            } 
        }
    //} 
};

InternalSocket.prototype.readyState = function () {
    if (this._socket === null) return 3;
    return this._socket.readyState;
};

InternalSocket.prototype.send = function (msgid, msg) {
     if (this._socket) {
        var msgroot = cc.protoRoot[msgid];
        if (msgroot) {
            var message = msgroot.create(msg);
            var buf = msgroot.encode(message).finish();
            var finalBuffer = new ArrayBuffer(2 + buf.byteLength);
            var dv = new DataView(finalBuffer);
            dv.setInt16(0, msgid, false);
            var offset = 2;
            for (let byte of buf) {
                dv.setUint8(offset, byte, true);
                offset++;
            }
            this._socket.send(finalBuffer);
            if (msgid == 1) msgid = "heartbeat";
            cc.log("send msgid ---> " + msgid + " : " + msgroot.name);
        }
     }
     else {
        //cc.log("send msg " + msgid + " failed.");
     }
};

InternalSocket.prototype.clearReconnetTimer = function () {
    if (this._reconnetTimer !== null) {
        clearInterval(this._reconnetTimer);
        this._reconnetTimer = null;
    }
};

InternalSocket.prototype.clearHBTimer = function () {
    if (this._heartBeatTimer !== null) {
        clearInterval(this._heartBeatTimer);
        this._heartBeatTimer = null;
    }
};

InternalSocket.prototype.heartBeat = function () {
    var self = this;
    if (self._heartBeatTimer === null) {
        self._heartBeatTimer = setInterval(function () {
            self.send(cc.MSG.HEARTBEAT, {});
        }, 30000);
    }
};

InternalSocket.prototype.close = function () {
    if (this._socket !== null)
        this._socket.close();
};

InternalSocket.prototype.addHandler = function (msgid, cb, fileName) {
    var self = this;
    if (fileName == undefined) {
        cc.log("addHandler filename undefined .");
    }
    else {
        if (self.handlers[msgid] == undefined)
            self.handlers[msgid] = [];
        var arr = self.handlers[msgid];
        for (var i = 0; i < arr.length; i++) {
            if (arr[i].fileName == fileName) {
                cc.log(fileName + " " + msgid + " already register.");
                arr[i].cb = cb;
                return;
            }
        }
        self.handlers[msgid].push({ fileName: fileName, cb: cb });
    }
};

InternalSocket.prototype.removeHandler = function (fileName) {
    var self = this;
    for (var msgid in self.handlers) {
        var arr = self.handlers[msgid];
        for (var i = 0; i < arr.length; i++) {
            if (arr[i].fileName == fileName) {
                arr.splice(i, 1);
                i--;
            }
        }
    }
};

exports.createNetClient = function(url){
    cc.log("createNetClient");
    return new InternalSocket(url);
};

exports.loadProto = function (cb) {
    var protoroot = {};
    var count = 0;
    var _loadProto = function (name, file_name) {
        try {
            protobuf.load(file_name, function (err, root) {
                cc.log("protobuf.load return " + file_name);
                if (err)
                    throw err;
                try {
                    var objs = cc.PTOTO[name];
                    for (var msgid in objs) {
                        cc.log(msgid);
                        protoroot[msgid] = root.lookupType(objs[msgid]);
                    }
                    count--;
                    done();
                }
                catch (err) {
                    throw err;
                }
            });
        }
        catch (err) {
            throw err;
        }
    };
    var done = function () {
        if (count === 0)
            cb(protoroot);
    };
    var arr = Object.keys(cc.PTOTO);
    if (arr.length == 0) done();

    for (var name in cc.PTOTO) {
        var filename = "proto/" + name + "";
        cc.log("load " + filename);
        count++;
        _loadProto(name, filename);
    }
};

exports.httpGet = function (url) {
    //
};

exports.httpPost = function (url, data, cb) {
    var xhr = cc.loader.getXMLHttpRequest();
    xhr.timeout = 5000;
    if (data === null) data = {};
    //发送请求
    xhr.open("POST", url, true);
    if (cc.sys.isNative) {
        xhr.setRequestHeader("Content-type", "application/json");
    }
    else {
        xhr.setRequestHeader("Content-type", "application/json");
        xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
    }
    var _cb = function (_data) {
        if (cb) {
            cb(_data);
            cb = null;
        }
    };

    var outTimer = setTimeout(function() {
        cc.log('http post timeout.');
        xhr.abort();
    }, 8000);

    xhr.onreadystatechange = function () {
        clearTimeout(outTimer);
        if (xhr.readyState === 4 && xhr.status == 200) {
            var resText = xhr.responseText;
            var ret = null;
            try {
                ret = JSON.parse(resText);
            } catch (e) {
                cc.log("http post err:" + e);
            }
            _cb(ret);
        }
        else if (xhr.readyState === 4) {
            _cb(null);
        }
        else {
            cc.log('readystate == ' + xhr.readyState  + ', status:' + xhr.status);
        }
        /*
        if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)) {

        }
        else {
            
            //_cb(null);
        }
        */
    };

    try {
        xhr.send(JSON.stringify(data));
    }
    catch (e) {
        cc.log("http post err:" + e);
        _cb(null);
    }
    return xhr;
};
